package executor

import (
	"fmt"
	"reflect"
	"strconv"

	"gitee.com/zhi/binexpr/ast"
)

type funcParam struct {
	executor *Executor
	expr     ast.Expr
	scope    Scope
}

func (p *funcParam) ToValue() (any, error) {
	return p.executor.Execute(p.expr, p.scope)
}

func (p *funcParam) ToBool() (bool, error) {
	val, err := p.ToValue()
	if err != nil {
		return false, err
	}
	if bVal, ok := val.(bool); ok {
		return bVal, nil
	}
	return val != nil, nil
}

func (p *funcParam) ToInt64() (int64, error) {
	val, err := p.ToValue()
	if err != nil {
		return 0, err
	}
	switch iVal := val.(type) {
	case int:
		return int64(iVal), nil
	case int8:
		return int64(iVal), nil
	case int16:
		return int64(iVal), nil
	case int32:
		return int64(iVal), nil
	case int64:
		return iVal, nil
	case uint:
		return int64(iVal), nil
	case uint8:
		return int64(iVal), nil
	case uint16:
		return int64(iVal), nil
	case uint32:
		return int64(iVal), nil
	case uint64:
		return int64(iVal), nil
	case float32:
		return int64(iVal), nil
	case float64:
		return int64(iVal), nil
	case string:
		return strconv.ParseInt(iVal, 10, 64)
	default:
		return 0, fmt.Errorf("unsupported value: %v", val)
	}
}

func (p *funcParam) ToUint64() (uint64, error) {
	val, err := p.ToValue()
	if err != nil {
		return 0, err
	}
	switch iVal := val.(type) {
	case int:
		return uint64(iVal), nil
	case int8:
		return uint64(iVal), nil
	case int16:
		return uint64(iVal), nil
	case int32:
		return uint64(iVal), nil
	case int64:
		return uint64(iVal), nil
	case uint:
		return uint64(iVal), nil
	case uint8:
		return uint64(iVal), nil
	case uint16:
		return uint64(iVal), nil
	case uint32:
		return uint64(iVal), nil
	case uint64:
		return iVal, nil
	case float32:
		return uint64(iVal), nil
	case float64:
		return uint64(iVal), nil
	case string:
		return strconv.ParseUint(iVal, 10, 64)
	default:
		return 0, fmt.Errorf("unsupported value: %v", val)
	}
}

func (p *funcParam) ToFloat64() (float64, error) {
	val, err := p.ToValue()
	if err != nil {
		return 0, err
	}
	switch iVal := val.(type) {
	case int:
		return float64(iVal), nil
	case int8:
		return float64(iVal), nil
	case int16:
		return float64(iVal), nil
	case int32:
		return float64(iVal), nil
	case int64:
		return float64(iVal), nil
	case uint:
		return float64(iVal), nil
	case uint8:
		return float64(iVal), nil
	case uint16:
		return float64(iVal), nil
	case uint32:
		return float64(iVal), nil
	case uint64:
		return float64(iVal), nil
	case float32:
		return float64(iVal), nil
	case float64:
		return iVal, nil
	case string:
		return strconv.ParseFloat(iVal, 64)
	default:
		return 0, fmt.Errorf("unsupported value: %v", val)
	}
}

func (p *funcParam) ToString() (string, error) {
	val, err := p.ToValue()
	if err != nil {
		return "", err
	}
	if sVal, ok := val.(string); ok {
		return sVal, nil
	}
	return fmt.Sprintf("%s", val), nil
}

func loadFuncParam(pd FuncParam, rVal reflect.Value) error {
	if !rVal.CanSet() {
		return fmt.Errorf("parameter type %s is not settable", rVal.Type())
	}
	switch rVal.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		iVal, err := pd.ToInt64()
		if err != nil {
			return err
		}
		rVal.SetInt(iVal)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		iVal, err := pd.ToUint64()
		if err != nil {
			return err
		}
		rVal.SetUint(iVal)
	case reflect.Float32, reflect.Float64:
		fVal, err := pd.ToFloat64()
		if err != nil {
			return err
		}
		rVal.SetFloat(fVal)
	case reflect.String:
		fVal, err := pd.ToString()
		if err != nil {
			return err
		}
		rVal.SetString(fVal)
	default:
		val, err := pd.ToValue()
		if err != nil {
			return err
		}
		pVal := reflect.ValueOf(val)
		if !pVal.CanConvert(rVal.Type()) {
			return fmt.Errorf("uncompatible type of %t", val)
		}
		rVal.Set(pVal)
	}
	return nil
}

func loadFuncParams(pds FuncParams, params ...any) error {
	if len(params) != len(pds) {
		return fmt.Errorf("not enough parameters to call")
	}
	for i, p := range params {
		rPointer := reflect.ValueOf(p)
		if rPointer.Kind() != reflect.Pointer {
			return fmt.Errorf("the parameters should be pointers")
		}
		pd := pds[i]
		rVal := rPointer.Elem()
		if err := loadFuncParam(pd, rVal); err != nil {
			return err
		}
	}
	return nil
}

func loadFuncParamsToPtrs(pds FuncParams, params ...reflect.Value) error {
	if len(params) != len(pds) {
		return fmt.Errorf("not enough parameters to call")
	}
	for i, p := range params {
		pd := pds[i]
		if err := loadFuncParam(pd, p); err != nil {
			return fmt.Errorf("Parameter[%d]: %w", i, err)
		}
	}
	return nil
}

func newFuncDef(exec func(params FuncParams) (any, error)) *FuncDef {
	return &FuncDef{eval: exec}
}

var errorType = reflect.TypeOf((*error)(nil)).Elem()

func newFuncDefFromReflect(fnVal reflect.Value) (*FuncDef, error) {
	if fnVal.Kind() != reflect.Func {
		return nil, fmt.Errorf("unsupported reflected function")
	}
	fnType := fnVal.Type()
	if fnType.IsVariadic() {
		return nil, fmt.Errorf("variadic function is unsupported yet")
	}
	if fnType.NumOut() != 2 || !fnType.Out(1).Implements(errorType) {
		return nil, fmt.Errorf("return types must be in the form of (type, error)")
	}
	return newFuncDef(func(params FuncParams) (any, error) {
		inputs := make([]reflect.Value, fnType.NumIn())
		for i := 0; i < fnType.NumIn(); i++ {
			inputs[i] = reflect.New(fnType.In(i)).Elem()
		}
		if err := loadFuncParamsToPtrs(params, inputs...); err != nil {
			return nil, err
		}
		returns := fnVal.Call(inputs)
		ret, err := returns[0].Interface(), returns[1].Interface()
		if err != nil {
			return ret, err.(error)
		}
		return ret, nil
	}), nil
}

func executeFunctionExpr(e *Executor, expr *ast.FuncExpr, scope Scope) (any, error) {
	rawFn, err := e.Execute(expr.Name, scope)
	if err != nil {
		return nil, err
	}
	fn, ok := rawFn.(*FuncDef)
	if !ok || fn.eval == nil {
		return nil, fmt.Errorf("unexpected type of function: %T", fn)
	}
	var params []FuncParam
	for _, p := range expr.Params {
		params = append(params, &funcParam{executor: e, expr: p, scope: scope})
	}
	return fn.eval(params)
}
